//===============================================================================
// Microsoft patterns & practices
// SharePoint Guidance version 2.0
//===============================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Security.Permissions;
using System.Text;
using System.Web;

using Microsoft.Practices.SPG.Common.ServiceLocation;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Security;

namespace Microsoft.Practices.SPG.Common.Logging
{
    /// <summary>
    /// Class that does the logging for a sharepoint environment.
    /// 
    /// This implementation will log diagnostic messages to the tracelog and
    /// errors to both the eventlog and the tracelog
    /// </summary>
    public class SharePointLogger : BaseLogger
    {
        private ITraceLogger traceLogger;

        /// <summary>
        /// The logger that get's tracemessages written to. 
        /// </summary>
        public ITraceLogger TraceLogger
        {
            get
            {
                if (traceLogger == null)
                {
                    traceLogger = SharePointServiceLocator.Current.GetInstance<ITraceLogger>();
                }

                return traceLogger;

            }
        }

        private IEventLogLogger eventLogLogger;

        /// <summary>
        /// The logger that get's errormessages written to. 
        /// </summary>
        public IEventLogLogger EventLogLogger
        {
            get
            {
                if (eventLogLogger == null)
                {
                    this.eventLogLogger = SharePointServiceLocator.Current.GetInstance<IEventLogLogger>();
                }

                return eventLogLogger;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SharePointLogger"/> class.
        /// </summary>
        public SharePointLogger()
        {
            
        }

        /// <summary>
        /// Writes a log message. Based on the Log Severity, it will write a log message to the EventLog and / or the tracelog.
        /// </summary>
        /// <param name="message">The message to log.</param>
        /// <param name="eventId">
        /// The eventId that corresponds to the event. This value, coupled with the EventSource is often used by
        /// administrators and IT PRo's to monitor the eventlog of a system. 
        /// </param>
        /// <param name="severity">How serious the event is. </param>
        /// <param name="category">The category of the logmessage.</param>
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void Log(string message, int eventId, TraceSeverity severity, string category)
        {
            string errorMessage = message + this.BuildContextualInformationMessage();

            Exception eventLogException = LogToEventLog(errorMessage, eventId, severity, category);

            Exception traceLogException = LogToTraceLog(errorMessage, eventId, severity, category);

            if (traceLogException != null && eventLogException == null)
            {
                AttemptToWriteTraceExceptionToEventLog(traceLogException);
            }

            if (eventLogException != null || traceLogException != null)
            {
                throw BuildLoggingException(errorMessage, eventLogException, traceLogException);
            }
        }

        private string BuildContextualInformationMessage()
        {
            

            if (HttpContext.Current == null)
                return string.Empty;

            StringBuilder builder = new StringBuilder();
            builder.Append("\nAdditional Information:");
            builder.AppendFormat("\n\tRequest TimeStamp: '{0}'", HttpContext.Current.Timestamp.ToString("o", CultureInfo.CurrentCulture));

            if (HttpContext.Current.User != null && HttpContext.Current.User.Identity != null)
            {
                builder.AppendFormat("\n\tUserName: '{0}'", HttpContext.Current.User.Identity.Name);
            }

            HttpRequest request = HttpContext.Current.Request;
            if (request != null)
            {
                builder.AppendFormat("\n\tRequest URL: '{0}'", request.Url);
                builder.AppendFormat("\n\tUser Agent: '{0}'", request.UserAgent);
                builder.AppendFormat("\n\tOriginating IP address: '{0}'", request.UserHostAddress);
            }
            
            return builder.ToString();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",Justification = "We have to ignore all exceptions here. It's a best attempt at logging.")]
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] 
        private void AttemptToWriteTraceExceptionToEventLog(Exception exception)
        {
            try
            {
                this.EventLogLogger.LogException(exception, 0, TraceSeverity.High, "Logging");
            }
            catch (Exception)
            {
                // We are only attempting to write the fact that the tracing failed to the eventlog. If that fails
                // we'll just bubble up the original exception
            }
        }

        private Exception BuildLoggingException(string originalMessage, Exception errorLogException, Exception traceLogException)
        {
            string errorMessage = "One or more error occured while writing messages into the log.";
            Exception innerException = null;

            // Build a dictionary with exception data, that can later be added to the exception
            Dictionary<object, object> exceptionData = new Dictionary<object, object>();
            exceptionData["OriginalMessage"] = originalMessage;

            // If there was an exception with logging to the exception log, add that data here:
            if (errorLogException != null)
            {
                string exceptionMessage = BuildExceptionMessage(errorLogException, null);
                errorMessage += "\r\nThe error while writing to the EventLog was:" + exceptionMessage;
                exceptionData["ErrorLogException"] = exceptionMessage;

                // Preferably, use the exception from the error log as the inner exception
                innerException = errorLogException;
            }

            // If there was an exception while logging to the trace log, add that data here. 
            if (traceLogException != null)
            {
                string exceptionMessage = BuildExceptionMessage(traceLogException, null);
                errorMessage += "\r\nThe error while writing to the TraceLog was:" + exceptionMessage;

                exceptionData["TraceLogException"] = exceptionMessage;

                // If the exception log didn't throw an exception, then use the exception 
                if (innerException == null)
                    innerException = traceLogException;
            }

            // Now build the exception with the gathered information. 
            LoggingException loggingException = new LoggingException(errorMessage, innerException);
            foreach(object key in exceptionData.Keys)
            {
                loggingException.Data.Add(key, exceptionData[key]);
            }

            return loggingException;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We have to catch exception here, because we want to handle all exceptions")]
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] 
        private Exception LogToEventLog(string message, int eventId, TraceSeverity severity, string category)
        {
            Exception loggingException = null;
            try
            {
                if (ShouldLogToEventLog(severity))
                {
                    EventLogLogger.Log(message, eventId, severity, category);
                }

            }
            catch (Exception ex)
            {
                loggingException = ex;
            }
            return loggingException;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We have to catch exception here, because we want to handle alle exceptions. ")]
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        private Exception LogToTraceLog(string message, int eventId, TraceSeverity severity, string category)
        {
            Exception loggingException = null;
            try
            {
                TraceLogger.Log(message, eventId, severity, category);
            }
            catch (Exception ex)
            {
                loggingException = ex;
            }
            return loggingException;
        }

        /// <summary>
        /// Determines if a message should be logged to the eventlog, based on the severity. 
        /// The default implementation will write High and Medium severity messages to the EventLog
        /// </summary>
        /// <param name="severity">The severity that determines if the message should go to the eventlog</param>
        /// <returns>true if the message should go into the eventlog</returns>
        protected virtual bool ShouldLogToEventLog(TraceSeverity severity)
        {
            return (severity == TraceSeverity.High
                    || severity == TraceSeverity.Medium);
        }
    }
}
